Skip to content

Q:

我之前说想让代码遵循 BFF 模式,应该是在前端做的吧,就是前端永远不会暴漏真实的后端请求 API 地址,它会自己在代码层做一层防腐,不过我这在后端代码写的 bff是啥

你的理解有一半是对的

你说的「前端 BFF」是什么?

你说的那种模式更准确地叫做 前端代理层 / API Proxy,即:

前端(比如 Next.js、Nuxt.js)自带一个 server 端,前端页面请求自己的 /api/xxx,然后 server 端再转发给真实的后端 API。这样浏览器里永远看不到真实后端地址。

这种确实是一种前端实现层面的 BFF,但它不是 BFF 的唯一形态。

那你后端写的这套 BFF 是什么?

你当前写的这套代码,其实是 后端 BFF(Backend For Frontend) 的经典形态,而且是完全合理的、业界主流的做法。

看你的代码结构:

trigger/http/
├── ActivityController.java         ← 通用/管理后台 API
├── AuthController.java
├── UserController.java
├── ...
└── bff/                            ← 小程序专用 API
    ├── AbstractBffController.java
    ├── BffActivityController.java  ← /api/v1/bff/activity
    ├── BffAuthController.java      ← /api/v1/bff/auth
    └── BffExceptionHandler.java    ← BFF 专用异常处理

你做的事情非常典型:

BFF 特征你代码里的体现
面向特定端裁剪数据BffActivityListItemDTO 只返回小程序需要的字段,而不是 ActivityAggregate 全量
聚合多个领域数据convertToDetail() 里聚合了活动信息 + 当前用户角色(CREATOR/PARTICIPANT/VISITOR)
响应格式统一BffResponse(code=0 成功)与小程序前端 request.js 拦截器对齐
独立异常处理BffExceptionHandler 只拦截 bff 包,不影响其他 Controller
路径隔离/api/v1/bff/* 与通用 API 分离

BFF 模式的两种主要实现位置

┌──────────────────────────────────────────────────────────────┐
│                       方式一:前端 BFF                         │
│                                                              │
│  浏览器 → Next.js /api/proxy → 转发 → 后端真实API               │
│                                                              │
│  优点:隐藏后端地址、前端团队自主控制                              │
│  适用:Web 应用(Next.js/Nuxt.js 有 server 端能力的场景)         │
└──────────────────────────────────────────────────────────────┘

┌──────────────────────────────────────────────────────────────┐
│                    方式二:后端 BFF(你现在的)                  │
│                                                              │
│  小程序/App → /api/v1/bff/* → BffController → Domain Service  │
│                                                              │
│  优点:数据裁剪、聚合在后端完成、减少网络请求、类型安全               │
│  适用:小程序、移动端(没有自己的 server 层做代理)                 │
└──────────────────────────────────────────────────────────────┘

关键区别

  • 前端 BFF:主要目的是「代理 + 隐藏真实后端地址」,更多是安全防腐层
  • 后端 BFF:主要目的是「按端裁剪数据 + 聚合 + 统一响应格式」,是接口适配层

对你这个项目的建议

你这是微信小程序项目,小程序本身没有 server 端能力(不像 Next.js),所以:

  1. 你在后端写 BFF 是完全正确的做法。小程序直接请求 /api/v1/bff/*,由后端 BFF 层做数据裁剪和聚合,这就是 BFF 模式的标准实践。
  2. 如果你还想隐藏真实后端地址,那不是 BFF 的事了,而是通过以下手段实现:
    • API 网关(如 Nginx 反向代理、Spring Cloud Gateway):小程序请求 https://api.yourapp.com/,网关转发到内部服务
    • 小程序服务端域名配置:微信小程序本身只允许配置白名单域名,天然就有一层隔离
  3. 你现有的 ActivityController(1029 行的那个通用版)和 BffActivityController 的共存是合理的——前者可能给管理后台用,后者给小程序用,各自按需裁剪。

总结:你后端写的 BFF 没有写错,它就是 BFF 模式在后端的经典实现。前端 BFF(代理隐藏地址)和后端 BFF(数据裁剪聚合)是同一模式的不同实现位置,不矛盾。

Q:

那这样的话,我怎么区分哪些接口需要裁剪了。我确实有 web 端的功能开发需求,但是现在还没进行开发,这些接口数据都是针对小程序的,目前只是写了活动和认证的 bff Controller,那其他的业务模块了,不需要加 bff吗,但是加了之后不是有两套接口了吗。虽然 web 接口后续需要,我不知道咋整了,你说说你的看法,还有就是和 DDD 冲突吗,这个 BFF 模式,我以前还没听过 BFF模式,还不知道需要在后端也加,一般我都是在前端加 BFF 然后 nginx 转发,不让用户看到我的真实请求地址。比如我的创建活动 在后端是 /creat/activity ,前端不会直接去调用这个,会做一个 bff 比如 /activity/create 这样别人就看不到我真实的请求地址,可以防止别人攻击我的接口

你提了好几个很好的问题,我逐个分析。


一、你说的前端 BFF(Nginx 转发改路径),严格来说不叫 BFF

你描述的这个:

后端是 /create/activity,前端做一层 /activity/create,通过 Nginx 反向代理转发,隐藏真实路径

这其实叫 API 网关 / 反向代理,核心目的是安全防护(隐藏内部路径、防止直接攻击)。它不做数据裁剪、不做聚合,只是路径映射 + 转发。

真正的 BFF 模式是 Sam Newman(微服务之父)提出的,核心含义是:

为每种前端(Frontend)量身定做一套后端接口(Backend),按前端的需要裁剪数据、聚合多服务、统一格式。

所以你之前理解的 "BFF" 更准确地说是 "反向代理防腐层",和真正的 BFF 不是一个东西。


二、你当前的核心困惑:要不要所有模块都加 BFF?会不会搞成两套接口?

先看你现在的接口分布

trigger/http/
├── ActivityController.java      ← 通用接口(1029行,功能很全)
├── AuthController.java
├── UserController.java
├── MatchingController.java
├── NotificationController.java
├── ...
└── bff/
    ├── BffActivityController.java   ← 小程序专用(裁剪版)
    └── BffAuthController.java       ← 小程序专用

问题很明显:如果每个模块都加 BFF,你就维护两套 Controller,改一个业务逻辑要改两处。

我的建议:你现在不需要 BFF 层

原因很简单——

BFF 模式解决的是「一个后端同时服务多个差异化前端」的问题。 比如:

需要的活动数据
小程序标题、地点、时间、是否已满、我的角色
Web 管理后台全量字段 + 审核记录 + 操作日志 + 统计
第三方开放 API脱敏后的公开数据

你现在只有小程序一个端,Web 端还没开始开发,此刻做 BFF 就是提前分裂,没有收益只有成本。

三、我推荐的方案:删掉 BFF 层,统一用一套 Controller

架构演进路径

阶段一(现在):只有小程序
─────────────────────────────
小程序 → Nginx → /api/v1/activity/*
                  /api/v1/auth/*
                  /api/v1/user/*

一套 Controller,直接服务小程序。
响应格式统一用 Response<T>(你现有的就行)。
不需要 BFF。

阶段二(将来):小程序 + Web 管理后台
─────────────────────────────
小程序    → /api/v1/app/activity/*     ← 面向 C 端用户
Web后台   → /api/v1/admin/activity/*   ← 面向管理员

这时才需要按端分 Controller,因为数据差异真的出现了。

这和 DDD 完全不冲突

看你的架构分层:

Trigger 层(Controller)  ← 只负责协议转换、参数校验
    ↓ 调用 Input Port
App 层(ApplicationService)  ← 编排业务

Domain 层  ← 核心逻辑

不管你有几套 Controller,它们都调用同一个 IActivityApplicationService。BFF 只是 Trigger 层的组织方式,和 DDD 的 Domain 层、App 层完全无关。

BffActivityController ──┐
                        ├──→ IActivityApplicationService → Domain
ActivityController ─────┘

DDD 中 Trigger 层本来就可以有多个适配器(Controller、Job、MQ Listener),按端分 Controller 只是多一种适配器而已。


四、关于安全防护(隐藏真实接口路径)

这个需求是合理的,但不该用 BFF 来做,正确的手段是:

1. Nginx 反向代理(你熟悉的方式)

# 小程序只知道 api.yourapp.com
# 真实后端在内网 192.168.x.x:8080

location /api/ {
    proxy_pass http://192.168.x.x:8080/api/;
}

小程序请求 https://api.yourapp.com/api/v1/activity/list,用户根本不知道后端在哪。而且路径不需要改,因为攻击者拿到路径也没用——关键是鉴权(JWT)和限流。

2. 微信小程序本身的安全机制

  • 小程序后台必须配置服务器域名白名单,只允许请求你配的域名
  • 小程序代码发布时会编译混淆,不像浏览器 F12 那么容易看到请求
  • 真正的安全靠:JWT 鉴权 + 接口限流 + 参数校验 + 权限控制,而不是隐藏路径

3. 路径混淆没有实际安全价值

"把 /create/activity 改成 /activity/create 防止攻击"

说实话,这层防护意义不大。攻击者通过抓包(Charles/Fiddler)几秒就能拿到真实路径,不管你怎么重映射。真正的安全防线是:

  • 每个请求校验 JWT Token
  • 关键操作校验权限(你已经在做了:getCurrentUserId(token)
  • 接口限流防刷
  • 输入参数严格校验

五、总结建议

问题建议
其他模块要不要加 BFF?不要,现在只有小程序一个端,没必要
会不会两套接口?当前阶段保持一套,等 Web 端开发时再按端拆分
BFF 和 DDD 冲突吗?完全不冲突,BFF 只是 Trigger 层的组织方式
怎么隐藏真实接口?用 Nginx 反向代理 + JWT 鉴权 + 限流,不靠改路径
现在写的 BFF 代码怎么办?建议把 BFF 的逻辑合并回主 Controller,统一一套接口。BffResponse 那套统一响应格式可以保留改成通用的 Response<T>

简单说:BFF 是解决多端差异化的模式,单端场景下只会增加维护成本。等你真的有 Web 后台需要不同数据结构时,再按端拆 Controller 也不迟。

Powered by VitePress